package com.yeskery.boot.log.record.starter.core;

import com.yeskery.boot.log.record.starter.annotation.LogRecord;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * 日志记录注解{@link LogRecord} 的解析类
 * @author dushuangjiang
 * @date 2021-11-11 18:31
 */
public class LogRecordOperationSource {

    /** 日志分隔符 */
    private static final char IDENTIFIER = '`';

    /** SpEL前缀 */
    private static final char SPEL_PREFIX = '#';

    /** CF前缀 */
    private static final char CF_PREFIX = '{';

    /** CF后缀 */
    private static final char CF_SUFFIX = '}';

    /**
     * 计算模板中，需要执行日志参数执行的日志记录语句操作对象
     * @param method 被标注为 {@link LogRecord} 的方法
     * @return 日志记录语句操作对象集合
     */
    public Collection<LogRecordOps> computeLogRecordOperations(Method method) {
        LogRecord logRecord = method.getAnnotation(LogRecord.class);
        if (logRecord == null) {
            return Collections.emptyList();
        }
        Collection<LogRecordOps> operations = new LinkedList<>();
        Arrays.stream(LogRecord.class.getDeclaredMethods()).forEach(m -> {
            try {
                operations.addAll(findLogRecordOps(m.invoke(logRecord).toString()));
            } catch (Exception e) {
                throw new LogRecordException("Failed to get log record template", e);
            }
        });
        return operations;
    }

    /**
     * 查找日志记录语句操作对象
     * @param template 模板语句
     * @return 日志记录语句操作对象集合
     */
    public Collection<LogRecordOps> findLogRecordOps(String template) {
        Collection<LogRecordOps> operations = new LinkedList<>();
        int count = 1, lastIndex = -1, currentIndex;
        while ((currentIndex = template.indexOf(IDENTIFIER, lastIndex + 1)) > -1) {
            if (count++ % 2 == 0) {
                String statement = template.substring(lastIndex + 1, currentIndex);
                if (StringUtils.hasText(statement)) {
                    LogRecordOps logRecordOps = buildLogRecordOps(statement);
                    if (logRecordOps.getStatementType() == null) {
                        continue;
                    } else if (logRecordOps.getStatementType() == StatementType.CF) {
                        // 处理自定义函数包含SpEL的嵌套语句
                        int endIndex = logRecordOps.getStatement().indexOf(CF_PREFIX, 1);
                        if (endIndex < 0) {
                            endIndex = logRecordOps.getStatement().indexOf(CF_SUFFIX, 1);
                        }
                        logRecordOps.setFunctionName(logRecordOps.getStatement().substring(1, endIndex));
                        if (statement.indexOf(SPEL_PREFIX) > -1) {
                            Collection<LogRecordOps> nestLogRecordOps = findNestLogRecordOps(statement);
                            logRecordOps.setDependencies(nestLogRecordOps);
                        }
                    }
                    operations.add(logRecordOps);
                }
            }
            lastIndex = currentIndex;
        }
        return operations;
    }

    /**
     * 清除模板语句中的标识符
     * @param statement 模板语句
     * @return 处理后的模板语句
     */
    public String cleanIdentifier(String statement) {
        return statement.replaceAll(String.valueOf(IDENTIFIER), "");
    }

    /**
     * 获取自定义方法执行值
     * @param logRecordOps 日志记录语句操作对象
     * @return 自定义方法执行值
     */
    public String findFunctionExecuteValue(LogRecordOps logRecordOps) {
        if (logRecordOps.getStatementType() == StatementType.CF) {
            if (logRecordOps.getStatement().indexOf(CF_PREFIX, 1) > 0) {
                return logRecordOps.getStatement().substring(logRecordOps.getStatement().indexOf(CF_PREFIX, 1) + 1,
                        logRecordOps.getStatement().indexOf(CF_SUFFIX));
            } else {
                return null;
            }
        }
        return logRecordOps.getStatement();
    }

    /**
     * 查找嵌套日志记录语句操作对象
     * @param statement 模板语句
     * @return 日志记录语句操作对象集合
     */
    private Collection<LogRecordOps> findNestLogRecordOps(String statement) {
        Collection<LogRecordOps> operations = new LinkedList<>();
        Stack<Character> stack = new Stack<>();
        int index = 0;
        do {
            char c = statement.charAt(index++);
            if (c != CF_SUFFIX) {
                stack.push(c);
            } else {
                StringBuilder sb = new StringBuilder();
                while (stack.peek() != CF_PREFIX) {
                    sb.append(stack.pop());
                }
                stack.pop();
                if (sb.charAt(sb.length() - 1) == SPEL_PREFIX) {
                    LogRecordOps logRecordOps = buildLogRecordOps(sb.reverse().toString());
                    if (logRecordOps.getStatementType() != null) {
                        operations.add(logRecordOps);
                    }
                }
            }
        } while (!stack.isEmpty() && index < statement.length());
        return operations;
    }

    /**
     * 构建日志记录语句操作对象
     * @param statement 模板语句
     * @return 构建后的日志记录语句操作对象
     */
    private LogRecordOps buildLogRecordOps(String statement) {
        LogRecordOps logRecordOps = new LogRecordOps();
        logRecordOps.setStatement(statement);
        logRecordOps.setOriginalStatement(statement);
        char firstChar = statement.charAt(0);
        logRecordOps.setStatementType(firstChar == SPEL_PREFIX
                ? StatementType.SpEL : firstChar == CF_PREFIX ? StatementType.CF : null);
        return logRecordOps;
    }
}
